Skip to content

[pull] main from danny-avila:main#150

Merged
pull[bot] merged 14 commits into
innFactory:mainfrom
danny-avila:main
Jun 2, 2026
Merged

[pull] main from danny-avila:main#150
pull[bot] merged 14 commits into
innFactory:mainfrom
danny-avila:main

Conversation

@pull

@pull pull Bot commented Jun 2, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

danny-avila and others added 14 commits June 1, 2026 08:43
* ci: Sync Helm chart tags

* ci: Address Helm tag sync review
* chore: upgrade docker builds to node 24

* test: avoid array at in telemetry spec
* chore: upgrade vite for node 24

* fix: restore production vite boot

* fix: preserve dynamic pwa shell
The ROLES cache is a single process-wide store, but role documents are
per-tenant (unique index { name, tenantId }). getRoleByName checked the cache
by role name BEFORE the tenant-scoped DB read, so a warm entry written under
one tenant's context was served to another tenant — leaking that tenant's
permission bits into the other's authorization decisions.

Scope every ROLES cache key with scopedCacheKey(), which appends the active
tenantId from the AsyncLocalStorage tenant context. It is a no-op when no
tenant context is set (or under runAsSystem), so single-tenant deployments
behave exactly as before.

Adds role.cache.spec.ts with a real Map-backed cache: two tenants sharing a
role name receive their own permissions, the cache key is tenant-scoped, the
same-tenant fast path still avoids a second DB read, and single-tenant mode
still uses the unscoped key.
…3455)

getAppConfig caches per-principal merged config overrides under a key built by
overrideCacheKey(role, userId, tenantId). The key used the tenantId *argument*
only — but callers that go through the tenant middleware (the common path)
pass no explicit tenantId and rely on the AsyncLocalStorage tenant context.
Those calls were keyed under the shared '__default__' bucket, so the DB query
(correctly scoped to the ALS tenant by the Mongoose plugin) produced a merged
config that was then cached and served to the next tenant resolving the same
role/user — leaking model specs, endpoints, and interface flags across tenants.

Fall back to getTenantId() before '__default__' so the cache key reflects the
actual tenant scope (param or ALS). Tighten the strict-mode warning to fire
only when there is genuinely no tenant anywhere (param nor ALS), since the ALS
case is now scoped rather than defaulted. No-op for single-tenant deployments,
where getTenantId() is undefined and the key stays '__default__'.

Adds tests (real Map-backed cache) proving the ALS tenant scopes the key and
that two tenants resolving the same role each get their own config with no
cache collision.
Mongoose's distinct query operation was not in the tenant-isolation plugin's
hooked-operation list, so .distinct() (and .find(...).distinct(field), whose op
switches to 'distinct') ran unscoped — reading across all tenants. This affects
the ACL resource lookups (findAccessibleResources, findPublicResourceIds —
including PUBLIC 'shared-to-all' entries), agent category values, and random
prompt categories.

distinct IS a registerable query-middleware hook in Mongoose 8 (it is in
queryOperations), so the fix is to register the existing queryMiddleware for it
— one line. This keeps every call site as .distinct(), which is the established
FerretDB-compatible pattern (getRandomPromptGroups was deliberately built on
.distinct() rather than an aggregation stage for FerretDB support), and scopes
all distinct queries systemically with the same SYSTEM-context bypass and
strict-mode fail-closed behavior as the other operations.

Adds distinct cases to the plugin spec (including the find().distinct() op
switch and SYSTEM/no-context paths) plus behavioral tenant-isolation specs for
the ACL and category lookups; verified all fail without the hook.
Adds a coverage test that enumerates all registered models and asserts every
schema with a tenantId field has the tenant-isolation plugin applied (detected
via the global Symbol.for('librechat:tenantIsolation') marker the plugin sets),
with a single documented allowlist entry — SystemGrant, which scopes tenancy
manually. A future model that ships a tenantId field without the plugin (the
exact gap that lets data leak across tenants) now fails CI instead of shipping
silently.
* feat: use SecretInput for sensitive fields

* fix: align auth SecretInput styles

* chore: remove unused password i18n keys

* fix: align SecretInput controls

* fix: use SecretInput for dynamic credentials

* fix: reveal SecretInput controls on hover

* fix: align SecretInput eye icon and modernize controls

The wrapper was a flex container, so passing 'mb-2' on the input made it
contribute its margin to the wrapper's cross-axis size — the controls overlay
spanned the inflated height and centered the toggle 4px below the input's
true center. Switching the wrapper to a plain relative block collapses height
back to the input.

Also tightens the toggle/copy buttons (size-7 rounded-md with hover:bg-surface-hover)
and adds a focus ring on the input. Auth pages still override className/buttonClassName
so login/register styling is unchanged.

* fix: remove focus ring from SecretInput

* fix: keep green focus border on auth secret inputs

SecretInput's modernized default uses focus-visible:border-border-heavy and
hover:border-border-medium, which Tailwind emits after the auth pages' focus:
rules and overrides them. Auth pages now also declare focus-visible:border-green-500
and hover:border-border-light so cn()/twMerge resolves them as the winners
when classes are concatenated.

* feat: add optional sensitive flag to MCP customUserVars

Dynamic MCP credential fields all rendered as masked SecretInputs, which
also hid non-secret setup values like usernames, project keys, and URLs.

Add an optional `sensitive` flag to customUserVars and the plugin auth
config. It defaults to masked when omitted, so existing configs keep the
safe-by-default behavior; set `sensitive: false` to render a field as
plain text. The flag is display-only — values remain encrypted at rest.
* fix: Preserve custom endpoint reasoning params

* fix: Address custom reasoning review cases

* fix: Format configured reasoning defaults

* fix: Honor dropped reasoning params

* fix: Configure custom reasoning response key
…ability Paths (#13461)

* chore: reduce auth and balance operational noise

* chore: tighten balance and capability noise handling

* chore: avoid balance 404s when disabled

* chore: use response locals for balance handoff
@pull pull Bot locked and limited conversation to collaborators Jun 2, 2026
@pull pull Bot added the ⤵️ pull label Jun 2, 2026
@pull pull Bot merged commit a86e504 into innFactory:main Jun 2, 2026
1 check passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants